package com.pl.redis;

import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;
import reactor.util.annotation.Nullable;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;

/**
 * redis相关操作
 *
 * @author shent
 */
@Service
public class RedisService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public void setString(String key, String value) {
        ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
        valueOperations.set(key, value);
    }

    public String getString(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    public Long incrString(String key) {
        return stringRedisTemplate.opsForValue().increment(key);
    }

    public void setHash(String key, String fieldKey, String value) {
        HashOperations<String, Object, Object> hashOperations = stringRedisTemplate.opsForHash();
        hashOperations.put(key, fieldKey, value);
    }

    public String getHash(String key, String fieldKey) {
        return (String) stringRedisTemplate.opsForHash().get(key, fieldKey);
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //list 集合相关开始
    public Long lPushList(String key, String value) {
        ListOperations<String, String> listOperations = stringRedisTemplate.opsForList();
        return listOperations.leftPush(key, value);
    }

    public Object rPopList(String key) {
        return stringRedisTemplate.opsForList().rightPop(key);
    }

    public Object rPopList(String key, long timeout, TimeUnit timeUnit) {
        return stringRedisTemplate.opsForList().rightPop(key, timeout, timeUnit);
    }


    public List<String> getList(String key, long start, long end) {
        return stringRedisTemplate.opsForList().range(key, start, end);
    }

    public Long getListLength(String key) {
        return stringRedisTemplate.opsForList().size(key);
    }

    public Object lIndexList(String key, long index) {
        return stringRedisTemplate.opsForList().index(key, index);
    }

    public void lTrimList(String key, long start, long end) {
        stringRedisTemplate.opsForList().trim(key, start, end);
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //set 集合相关开始
    //set 集合 无序性,唯一性

    /**
     * 通过 sadd 命令,
     * 给set集合添加元素
     *
     * @param key   键
     * @param value 值
     * @return 成功操作的条数
     */
    public Long setSetAdd(String key, String value) {
        return stringRedisTemplate.opsForSet().add(key, value);
    }

    /**
     * 通过 smembers 命令,
     * 获得对应 key 集合中的所有元素
     *
     * @param key 键
     * @return 对应 key 的 set 集合所有元素
     */
    public Set<String> getSetMembers(String key) {
        return stringRedisTemplate.opsForSet().members(key);
    }

    /**
     * 通过 sismember 命令,
     * 判断元素是否在集合中
     *
     * @param key    键
     * @param member 值
     * @return 在集合中true, 不在集合中flase
     */
    public Boolean isInSet(String key, String member) {
        return stringRedisTemplate.opsForSet().isMember(key, member);
    }

    /**
     * 通过 srem 命令,
     * 删除指定key,member对应的元素
     *
     * @param key    键
     * @param member 值
     * @return 删除成功的条数
     */
    public Long remSet(String key, String member) {
        return stringRedisTemplate.opsForSet().remove(key, member);
    }

    public Set<String> diffSet(String key, String otherKey) {
        return stringRedisTemplate.opsForSet().difference(key, otherKey);
    }
    //set 集合相关结束
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //zset 有序集合相关开始
    //这里有序集合 zset 包括 key,value,score 三个部分

    /**
     * 通过 zadd 命令,给有序集合添加元素
     *
     * @param key   键
     * @param value 值
     * @param score 分数
     * @return 成功添加到redis的个数
     */
    public Long setTreeSet(String key, String value, Double score) {
        Set<ZSetOperations.TypedTuple<String>> tuples = new TreeSet<>();
        ZSetOperations.TypedTuple<String> a = new ZSetOperations.TypedTuple<String>() {
            @Override
            public int compareTo(ZSetOperations.TypedTuple<String> o) {
                return 0;
            }

            @Override
            public String getValue() {
                return value;
            }

            @Override
            public Double getScore() {
                return score;
            }
        };
        tuples.add(a);
        return stringRedisTemplate.opsForZSet().add(key, tuples);
    }

    /**
     * 通过 zrange 命令,获取有序集合,分数从小到大排序,
     * 0集合从左往右第一个,-1集合从右往左第一个
     *
     * @param key   键
     * @param start 开始
     * @param end   结束
     * @return 有序集合
     */
    public Set<String> getTreeSet(String key, long start, long end) {
        return stringRedisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 通过 zrevrange 命令,获取有序集合,分数从大到小排序,
     * 0集合从左往右第一个,-1集合从右往左第一个
     *
     * @param key   键
     * @param start 开始
     * @param end   结束
     * @return 有序集合
     */
    public Set<String> getTreeSetRev(String key, long start, long end) {
        return stringRedisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    /**
     * 通过 zrangebyscore 命令,获取有序集合,分数从小到大排序,
     * 根据分数区间[min,max]获取有序集合
     *
     * @param key 键
     * @param min 最低分数
     * @param max 最高分数
     * @return 有序集合
     */
    public Set<String> getTreeSetByScore(String key, double min, double max) {
        return stringRedisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

    /**
     * 通过 zrevrangebyscore 命令,获取有序集合,分数从大到小排序,
     * 根据分数区间[min,max]获取有序集合
     *
     * @param key 键
     * @param min 最低分数
     * @param max 最高分数
     * @return 有序集合
     */
    public Set<String> getTreeSetByScoreRev(String key, double min, double max) {
        return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
    }

    /**
     * 通过 zincrby 命令,根据 key和 value 增加 score(score 可以为负数)
     *
     * @param key   键
     * @param value 值
     * @param score 分数
     * @return 修改后的分数
     */
    public Double incrTreeSet(String key, String value, double score) {
        return stringRedisTemplate.opsForZSet().incrementScore(key, value, score);
    }

    /**
     * 通过 zcore 命令,根据 key 和 value 获取分数
     *
     * @param key   键
     * @param value 值
     * @return 分数
     */
    public Double getTreeSetScoreByKeyAndMember(String key, String value) {
        return stringRedisTemplate.opsForZSet().score(key, value);
    }

    /**
     * 通过 zcard 命令,根据 key 来获取该 key 下元素的数量
     *
     * @param key 键
     * @return 对应 key 有序集合中元素的数量
     */
    public Long getTreeSetCountByKey(String key) {
        return stringRedisTemplate.opsForZSet().zCard(key);
    }

    /**
     * 通过 zcount 命令,
     * 获取对应 key 的区间[min,max]内元素个数
     *
     * @param key 键
     * @param min 最低分数
     * @param max 最高分数
     * @return 对应 key 的区间[min,max]内元素个数
     */
    public Long getTreeSetCountByKeyFromTo(String key, double min, double max) {
        return stringRedisTemplate.opsForZSet().count(key, min, max);
    }

    /**
     * 通过 zrem 命令,
     * 删除指定 key 和 value 的元素
     *
     * @param key   键
     * @param value 值
     * @return 成功执行的条数
     */
    public Long remTreeSet(String key, String value) {
        return stringRedisTemplate.opsForZSet().remove(key, value);
    }

    /**
     * 通过 remrangebyrank 命令,
     * 删除指定范围内的元素
     *
     * @param key   键
     * @param start 索引开始
     * @param end   索引结束
     * @return 成功删除的条数
     */
    public Long remRangeByRank(String key, long start, long end) {
        return stringRedisTemplate.opsForZSet().removeRange(key, start, end);
    }

    /**
     * 通过 remrangebyscore 命令
     * 删除指定分数范围内的元素
     *
     * @param key 键
     * @param min 最低分数
     * @param max 最高分数
     * @return 成功删除的条数
     */
    public Long remRangeByScore(String key, double min, double max) {
        return stringRedisTemplate.opsForZSet().removeRangeByScore(key, min, max);
    }

    /**
     * 通过 zrank 命令,
     * 获取对应键值元素的分数排名,从小到大(从0开始)
     *
     * @param key   键
     * @param value 值
     * @return 获取对应键值的分数排名
     */
    public Long getRank(String key, String value) {
        return stringRedisTemplate.opsForZSet().rank(key, value);
    }

    /**
     * 通过 zrevrank 命令,
     * 获取对应键值元素的分数排名,从大到小(从0开始)
     *
     * @param key   键
     * @param value 值
     * @return 获取对应键值的分数排名
     */
    public Long getRankRev(String key, String value) {
        return stringRedisTemplate.opsForZSet().reverseRank(key, value);
    }

    /**
     * 通过 zinterstore 命令,
     * 对多个有序集合,执行 交集 操作,把执行完成的结果存入一个有序集合 destKey
     * aggregate:
     * 为 MIN 时,结果集合中元素value的score选择最小的分数
     * 为 MAX 时,结果集合中元素value的score选择最大的分数
     * 为 SUM 时,结果集合中元素value的score为子集合中对应元素value所有分数的和
     * weights:分配各个集合的权重,每个集合相同元素的分数 乘上 权重之后对比
     *
     * @param key       执行 交集 操作的有序集合的 key
     * @param otherKeys 其他需要执行 交集 的集合的 key
     * @param destKey   结果存入有序集合
     * @param aggregate MIN/MAX/SUM {@link RedisZSetCommands.Aggregate}
     * @param weights   权重 {@link RedisZSetCommands.Weights}
     * @return
     */
    public Long interStoreTreeSet(String key, Collection<String> otherKeys, String destKey, RedisZSetCommands.Aggregate aggregate, RedisZSetCommands.Weights weights) {
        //设置权重
        //RedisZSetCommands.Weights.of(1,0.1,0.5,..);
        //设置分数选择策略
        //aggregate = RedisZSetCommands.Aggregate.MIN;
        return stringRedisTemplate.opsForZSet().intersectAndStore(key, otherKeys, destKey, aggregate, weights);
    }

    /**
     * 通过 zunionstore 命令,
     * 对多个有序集合,执行 并集 操作,把执行完成的结果存入一个有序集合 destKey
     * aggregate:
     * 为 MIN 时,结果集合中元素value的score选择最小的分数
     * 为 MAX 时,结果集合中元素value的score选择最大的分数
     * 为 SUM 时,结果集合中元素value的score为子集合中对应元素value所有分数的和
     * weights:分配各个集合的权重,每个集合相同元素的分数 乘上 权重之后对比
     *
     * @param key       执行 并集 操作的有序集合的 key
     * @param otherKeys 其他需要执行 并集 的集合的 key
     * @param destKey   结果存入有序集合
     * @param aggregate MIN/MAX/SUM {@link RedisZSetCommands.Aggregate}
     * @param weights   权重 {@link RedisZSetCommands.Weights}
     * @return
     */
    public Long unionStoreTreeSet(String key, Collection<String> otherKeys, String destKey, RedisZSetCommands.Aggregate aggregate, RedisZSetCommands.Weights weights) {
        //设置权重
        //RedisZSetCommands.Weights.of(1,0.1,0.5,..);
        //设置分数选择策略
        //aggregate = RedisZSetCommands.Aggregate.MIN;
        return stringRedisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey, aggregate, weights);
    }
    //zset 有序集合相关结束
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //等进阶

    /**
     * redis事务和 watch 机制
     * <p>
     * redis事务只能检测语法是否错误,不支持回滚,
     * 若遇到语法错误,事务会停止执行,
     * 若遇到运行错误则会继续执行,不会回滚
     * <p>
     * watch 命令 监控一个或多个 key 的值
     */
    public void multiAndWatch() {
        stringRedisTemplate.watch("");
        //开启事务
        stringRedisTemplate.multi();
        //...事务中待执行的命令,将其添加到队列中
        //执行事务
        stringRedisTemplate.exec();
        stringRedisTemplate.unwatch();
    }

    public void multi() {
        stringRedisTemplate.multi();
    }

    public void exec() {
        stringRedisTemplate.exec();
    }

    /**
     *
     */
    /**
     * 通过 expire key seconds 命令,
     * 设置 键 过期时间
     *
     * @param key        键
     * @param expireTime 过期时间值
     * @param timeUnit   单位
     * @return 执行成功返回 true
     */
    public Boolean setExpire(String key, long expireTime, TimeUnit timeUnit) {
        timeUnit = TimeUnit.MINUTES;
        return stringRedisTemplate.expire("w1", expireTime, timeUnit);
    }

    /**
     * 通过 ttl 命令,
     * 获取 键 的剩余过期时间
     *
     * @param key      键
     * @param timeUnit 单位
     * @return 剩余时间
     */
    public Long getExpireTime(String key, TimeUnit timeUnit) {
        timeUnit = TimeUnit.MINUTES;
        return stringRedisTemplate.getExpire(key, timeUnit);
    }

    /**
     * 通过 exists 命令,
     * 判断键是否存在
     *
     * @param key
     * @return
     */
    public Boolean isExists(String key) {
        return stringRedisTemplate.hasKey(key);
    }


    public void publish(String channel, String msg) {
        stringRedisTemplate.convertAndSend(channel, msg);

    }
}
